/**
 * @Filename: test_run.cpp
 * @Description:
 * @Author: chengxiaojun
 * @Email: csforgood@gmail.com
 * @Time: 2011/11/11
 */

#include <testcase.h>
#include <tinyxml/tinyxml.h>
#include <getopt.h>
#include <fileutil.h>
#include <pthread.h>
#include <log.h>
#include <dlfcn.h>
#include <map>
#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <stdlib.h>

#define VERSION    "worktest_0.2"
#define HELPINFO  "usage: ./" \
	VERSION \
"\n"            \
"--so <string>     : shared library path\n"    \
"-c                : case.xml\n" \
"--filter <string> : cases\n"    \
"--dry-run         : dump case information only\n" \
"--help -h         : show help\n" \
"--version -v      : show version\n"

using namespace std;
using namespace boost;

vector<CommonCaseST> testcases;
void *handle = NULL;

struct ArgST {

	char *szSoPath;
	char *szCase  ;
	char *szFilter ;
	bool dryRun ; 
	ArgST() {
		dryRun = false;
		szCase   = NULL;
		szFilter = NULL;
		szSoPath = NULL;
	}
	~ArgST() {
		if(szCase) delete szCase;
		if(szSoPath) delete szSoPath;
		if(szFilter) delete szFilter;
	}

}argMain;


void parseXML(const char* xmlFile, vector<CommonCaseST> &testcases);



void usage() {
	fprintf(stderr,"%s\n",HELPINFO);
	exit(1);
}

void dump() {

	for(int i=0; i< testcases.size();i++) {
		fprintf(stderr, "\nCase: %s\n", testcases[i].caseName.c_str());

		for(int j=0;j<testcases[i].ops.size();j++) {
			if(testcases[i].ops[j].opCheck) 
				fprintf(stderr,"  operation:\n  name:%s\n  type:%s\n  expect:%d\n  block:%d\n  sleep:%d\n", testcases[i].ops[j].opName.c_str(),  testcases[i].ops[j].opType.c_str(),testcases[i].ops[j].opExpect, testcases[i].ops[j].opBlock, testcases[i].ops[j].opWait);
			else 
				fprintf(stderr,"  operation:\n  name:%s\n  type:%s\n  block:%d\n  sleep:%d\n", testcases[i].ops[j].opName.c_str(),  testcases[i].ops[j].opType.c_str(), testcases[i].ops[j].opBlock, testcases[i].ops[j].opWait);
		}
	}
}

void processArgs(int argc, char** argv) {

	const char * const short_options = "f:c:o:vh";

	const struct option long_options[] = { 
		{ "help", 0, NULL, 'h' }, 
		{ "version", 0, NULL, 'v' }, 
		{ "filter",1,NULL,'f'},
		{ "so",1,NULL,'o'},
		{ "dry-run",0,NULL,'0'},
		{ 0, 0, 0, 0 },        
	};

	int option;
	char *p;
	while ((option = getopt_long(argc, argv, short_options, long_options, NULL))
			!= -1) {
		switch (option) {
			case 'v':
				usage();
				break;
			case 'h':
				usage();
				break;
			case 'o':
				argMain.szSoPath = strdup(optarg);
				break;
			case 'f':
				argMain.szFilter = strdup(optarg);
				break;
			case 'c':
				argMain.szCase = strdup(optarg);
				break;
			case '0':
				argMain.dryRun = true;
				break;
			default:
				usage();
		}
	}

}

void executeOperation(CommonOperationST *operation) {

	int &ret =  operation->opRet;
	if(operation->opLanguage == C) {
		operation_func opFunc = (operation_func)dlsym(handle, operation->opType.c_str());
		ret = opFunc(operation);
	}
	else if(operation->opLanguage == SHELL) {
		string &command = operation->opType;
		map<string,string> &arguments =  operation->opArgs;

		string cmd;
		std::vector<std::string> strs;
		boost::split(strs, command, boost::is_any_of("\t, "));
		for(int i = 0 ; i < strs.size(); i++) {
			if(strs[i][0] == '$') {
				string substr = strs[i].substr(1,strs[i].size()-1);
				if(substr.empty()) {
					ERROR_LOG("%s is not a valid variable", strs[i].c_str());
					ret = -1;break;
				}      
				string value = operation->lookupArgument(substr,string("0"));
				cmd += " ";
				cmd += value;
			}
			else {
				cmd += " ";
				cmd += strs[i];

			}
		}
		ret = system(cmd.c_str());
                ret = WEXITSTATUS(ret);
	}
	else {
		ERROR_LOG("unknown operation type");
		ret = -1;
	}
}

void showOperationResult(CommonOperationST *operation, CommonCaseST *testcase) {
	if(operation->opCheck) {
		if(operation->opRet == operation->opExpect) {
			STAT_GLOG("Operation: %s\tResult: %d Expect: %d\t"PASS,operation->opName.c_str(),operation->opRet,operation->opExpect);
		} else {
			testcase->caseRet = 0;
			STAT_GLOG("Operation: %s\tResult: %d Expect: %d\t"FAIL,operation->opName.c_str(),operation->opRet,operation->opExpect);
		}
	}
	else if(operation->opType != "wait")
		STAT_GLOG("Operation: %s\tResult: %d Expect: Nan", operation->opName.c_str(),operation->opRet);
}
int executeCase(CommonCaseST &testcase) {

	int opNum = testcase.ops.size();
	vector<thread*> pth(opNum);
	BillBoard board;
	for(int i = 0 ; i < opNum; i++) {pth[i] = NULL;}
	board.create();

	for(int i = 0 ; i < opNum; i++) {

		if(testcase.ops[i].opType == "wait" || testcase.ops[i].opLanguage != C) continue;
		operation_func opFunc = (operation_func)dlsym(handle, testcase.ops[i].opType.c_str());
		if(opFunc == NULL) {
			ERROR_GLOG("failed to find operation type:%s",testcase.ops[i].opType.c_str());
			return -1;
		}
	}
	for(int i = 0 ; i < opNum; i++) {
		sleep(testcase.ops[i].opWait);

		if(testcase.ops[i].opType == "wait") {
			for(int j = 0; j < i; j++) {
				if( testcase.ops[j].opBlock == false) {
					INFO_GLOG("Wait for operation[%d]: %s ...",j+1 ,testcase.ops[j].opName.c_str());
					pth[j]->join();
					showOperationResult(&(testcase.ops[j]), &testcase);
					testcase.ops[j].opBlock = true;
				}
			}
		}
		else {
			testcase.ops[i].board = &board;
			INFO_GLOG("Start operation: %s ...", testcase.ops[i].opName.c_str());
			thread *thr = new thread(bind(&executeOperation,&(testcase.ops[i])));
			//pth.push_back(thr);
			pth[i] = thr;
			if(testcase.ops[i].opBlock) {
				INFO_GLOG("Wait for operation[%d]: %s ...",i+1, testcase.ops[i].opName.c_str());
				thr->join();
			        showOperationResult(&(testcase.ops[i]), &testcase);
			}

		}
	}
	for(int i = 0; i < opNum; i++) {
		if( testcase.ops[i].opBlock == false) {
			INFO_GLOG("Wait for operation[%d]: %s ...", i+1,testcase.ops[i].opName.c_str());
			pth[i]->join();
			showOperationResult(&(testcase.ops[i]), &testcase);
		}
	}
	for(int i = 0 ; i< opNum;i++) {
		if(pth[i]) delete pth[i];
	}
	fprintf(stderr,"\n");
	board.show();
	return 0;
}


int main(int argc, char** argv) {
	processArgs(argc,argv);
	if(argMain.szCase == NULL || argMain.szSoPath == NULL) { usage();}

	if(!isFile(argMain.szSoPath)) { usage();}

	if(isFile(argMain.szCase)) {
		parseXML(argMain.szCase,testcases);
	} 
	else if(isDir(argMain.szCase)) {
		vector<string> allCases;
		scanDir(argMain.szCase,allCases);
		for(int i =0 ; i < allCases.size(); i++) {
			parseXML(allCases[i].c_str(),testcases);
		}
	}

	if(argMain.dryRun) { 
		dump();     
		exit(1);
	}

	handle = dlopen(argMain.szSoPath, RTLD_LAZY|RTLD_GLOBAL);
	if(handle == NULL) {
		ERROR_GLOG("%s",dlerror());
		exit(1);
	}

	global_func init = (global_func)dlsym(handle, "init");
	global_func finish = (global_func)dlsym(handle, "finish");

	if(init) init();
	for(int i = 0 ; i < testcases.size(); i++) {
		if(argMain.szFilter) {
			if(strcmp( testcases[i].caseName.c_str(), argMain.szFilter) == 0) { 
				INFO_GLOG("Execute Case: %s", testcases[i].caseName.c_str());
				int ret = executeCase(testcases[i]);           
				if(ret == -1) {
					return ret;   
				}
			}
		} 
		else { 
			INFO_GLOG("Execute Case: %s", testcases[i].caseName.c_str());
			executeCase(testcases[i]);           
		}
	}
	for(int i = 0 ; i < testcases.size(); i++) {
		if(testcases[i].caseRet == 1) {
			INFO_GLOG("Case:%s/%s\tResult:"PASS,testcases[i].casePath.c_str(),testcases[i].caseName.c_str());
		}
		else if(testcases[i].caseRet == 0) {
			INFO_GLOG("Case:%s/%s\tResult:"FAIL,testcases[i].casePath.c_str(),testcases[i].caseName.c_str());
		}
	}
	if(finish) finish();
	INFO_GLOG("Test finished",1);   

	dlclose(handle);
	pthread_exit(NULL);
}

void parseXML(const char* xmlFile, vector<CommonCaseST> &testcases) {

	TiXmlDocument doc;
	doc.LoadFile(xmlFile);

	TiXmlElement  *rootElement = doc.RootElement(); //suites
	if(strcmp("suites",rootElement->Value()) != 0) {
		fprintf(stderr,"unknown %s\n",rootElement->Value());
	}

	TiXmlElement  *suiteElement = rootElement->FirstChildElement() ; //suite

	while(suiteElement) {
		string suiteName;
		if(strcmp("suite",suiteElement->Value()) != 0) {
			fprintf(stderr,"unknown %s\n",suiteElement->Value());
		}
		TiXmlAttribute* attributeOfSuite = suiteElement->FirstAttribute();
		while ( attributeOfSuite) {
			if(strcasecmp(attributeOfSuite->Name(),"name") == 0) {
				suiteName = attributeOfSuite->Value();
			}
			else {
				fprintf(stderr,"unknow %s\n", attributeOfSuite->Name());
			}
			attributeOfSuite = attributeOfSuite->Next();
		}
		/* get cases in suite*/
		TiXmlElement  *caseElement = suiteElement->FirstChildElement();
		while(caseElement) {
			CommonCaseST testcase;     
			testcase.casePath = xmlFile;
			TiXmlAttribute* attributeOfCase = caseElement->FirstAttribute();
			while (attributeOfCase) {
				if(strcasecmp(attributeOfCase->Name(),"name") == 0) {
					testcase.caseName =  suiteName + "." + attributeOfCase->Value();
				}
				else if(strcasecmp(attributeOfCase->Name(),"description") == 0) {
					testcase.caseDesc = attributeOfCase->Value();
				}
				else {
					fprintf(stderr,"unknow %s\n", attributeOfCase->Name());
				}
				attributeOfCase = attributeOfCase->Next();
			}
			/* get operation */
			TiXmlElement  *opElement = caseElement->FirstChildElement();
			while(opElement) {
				/* parse an operation */
				CommonOperationST op;

				TiXmlAttribute* attributeOfOp = opElement->FirstAttribute();
				while (attributeOfOp) {
					if(0 == strcasecmp(attributeOfOp->Name(),"name")) {
						op.opName =  attributeOfOp->Value();                  
					}
					else if(0 == strcasecmp(attributeOfOp->Name(),"language")) {
						if(0 == strcasecmp("SHELL",attributeOfOp->Value())) {
							op.opLanguage =  SHELL;           
						}
					}
					else if(0 == strcasecmp(attributeOfOp->Name(),"type")) {
						op.opType =  attributeOfOp->Value();           
					}
					else if(0 == strcasecmp(attributeOfOp->Name(),"sleep")) {
						op.opWait =  atoi(attributeOfOp->Value());           
					}
					else if(0 == strcasecmp(attributeOfOp->Name(),"num")) {
						op.opNum =  atoi(attributeOfOp->Value());           
					}
					else if(0 == strcasecmp(attributeOfOp->Name(),"block")) {
						op.opBlock =  (0 == strcasecmp(attributeOfOp->Value(),"off"))?false:true;
					}
					else if(0 == strcasecmp(attributeOfOp->Name(),"expect")) {
						op.opCheck = true;
						op.opExpect =  atoi(attributeOfOp->Value());           
					} else {
						op.opCtrls[attributeOfOp->Name()] = attributeOfOp->Value();
					}
					attributeOfOp = attributeOfOp->Next();
				}
				TiXmlElement  *argElement  = opElement->FirstChildElement();
				while(argElement) {
					op.opArgs[argElement->Value()] = argElement-> GetText(); 
					argElement = argElement->NextSiblingElement();
				}
				for(int m=0;m<op.opNum;m++) { op.opID = m; testcase.ops.push_back(op); }
				opElement = opElement->NextSiblingElement();
			}  
			caseElement = caseElement->NextSiblingElement();
			testcases.push_back(testcase);
		}
		suiteElement = suiteElement->NextSiblingElement();
	}
}
